﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;
using System.Timers;

namespace DownloadInterface
{
    [Serializable()]
    class DownloadAction
    {
        private DownloadTask task;
        private List<DownloadThreadInfo> threadInfoes;
        private bool paused;
        //private DownloadManager dm;


        [field: NonSerialized]
        public DownloadCompletedEventHandler completeEvent;
        [field: NonSerialized]
        public DownloadErrorEventHandler errorEvent;

        private String tempFolder;

        //用于状态计算
        private long oneSecondPreviousBytesReceviced;
        //private long previousDurationsReceived;
        private long totalBytes;
        private DateTime previousStartTime;
        private long previousTotalDuration; //in seconds



        public DownloadAction(DownloadTask task, DownloadCompletedEventHandler completeEvent,
            DownloadErrorEventHandler errorEvent)
        {
            this.task = task;
            //this.dm = dm;
            threadInfoes = null;
            paused = false;
            this.completeEvent = completeEvent;
            this.errorEvent = errorEvent;
            oneSecondPreviousBytesReceviced = 0L;
            totalBytes = 0L;
            previousTotalDuration = 0L;
            tempFolder = "./temp/";
            //previousDurationsReceived = 0L;
            if(Directory.Exists(task.filePath) == false)
            {
                //throw new PathInvalidException();
                try
                {
                    Directory.CreateDirectory(task.filePath);
                }
                catch (System.Exception ex)
                {
                    throw new PathInvalidException();
                }
                
            }

            if(isValidFileName(task.fileName) == false)
            {
                throw new FileNameInvalidException();
            }
        }

        public bool startDownload() //必须先Divide再启动多线程下载 Divide时确定下载线程数
        {
            try
            {
                if (task.taskStatus.taskStatus != TaskStatus.STATUS_DOWNLOADING)
                {
                    oneSecondPreviousBytesReceviced = 0L;
                    task.taskStatus.taskStatus = TaskStatus.STATUS_DOWNLOADING;
                    paused = false;
                    if (threadInfoes == null)
                    {
                        threadInfoes = divideTask(3);
                    }
                    Thread download = new Thread(new ThreadStart(createMultiThreadToDownload));
                    download.Start();
                    previousStartTime = DateTime.Now;
                }
                return true;
            }
            catch (System.Net.WebException ex)
            {
                if (errorEvent != null)
                {
                    errorEvent(DownloadError.ErrorCode.DOWNLOAD_NETWORK_DISCONNECTED);
                }
                Console.WriteLine("无网络连接");
                return false;
            }

        }

        public void pauseDownload()//暂停
        {
            //previousDurationsReceived = 0;
            if(paused == false)
            {
                paused = true;
                foreach (DownloadThreadInfo dti in threadInfoes)
                {
                    dti.downloading = false;
                    //previousDurationsReceived += dti.downloadedBytes;
                }
                task.taskStatus.taskStatus = TaskStatus.STATUS_STOPPED;
                task.taskStatus.downloadCurrentSpeed = "0B/s";
                DateTime currentTime = DateTime.Now;
                previousTotalDuration += (currentTime - previousStartTime).Seconds;
            }
        }

        public void cancelDownload()
        {
            pauseDownload();
            Thread deleteTempFileThread = new Thread(new ThreadStart(deleteTempFiles));
            deleteTempFileThread.Start();
        }

        private void createMultiThreadToDownload() //用于创建线程
        {
            try
            {
                if (threadInfoes == null)
                {
                    throw new Exception();
                }
                foreach (DownloadThreadInfo dti in threadInfoes)
                {
                    Thread t = new Thread(new ParameterizedThreadStart(downloadBytes));
                    t.Start(dti);
                }

                System.Timers.Timer timer = new System.Timers.Timer(1000);
                //更新任务状态
                timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
                timer.Start();

                while (isTaskFinished() == false && paused == false)
                {
                    //Thread.Sleep(1000);
                }

                if (isTaskFinished())
                {
                    combineFiles();
                    if (completeEvent != null)
                    {
                        completeEvent(task);
                    }
                }

                //下载完毕



                timer.Stop();
                task.taskStatus.downloadCurrentSpeed = "0B/s";
                task.taskStatus.taskStatus = TaskStatus.STATUS_STOPPED;


                Console.WriteLine("文件大小:" + task.fileSize);
                Console.WriteLine("用时:" + task.taskStatus.downloadDuration);
                Console.WriteLine("平均速度:" + task.taskStatus.downloadAverageSpeed);
                Console.WriteLine("即时速度:" + task.taskStatus.downloadCurrentSpeed);


            }
            catch (ThreadAbortException e)
            {
                //return false;
            }
        }

        private bool isTaskFinished()
        {
            if (task == null)
            {
                return false;
            }
            if (threadInfoes == null)
            {
                return false; //不确定
            }
            else
            {
                foreach (DownloadThreadInfo dp in threadInfoes)
                {
                    if (dp.finished == false)
                    {
                        return false;
                    }
                }
                return true;
            }

        }

        private void combineFiles()
        {
            Console.WriteLine("开始合并文件");
            try
            {
                if (isTaskFinished() == false)//以防万一
                {
                    Console.WriteLine("还没下载完呢");
                    return;
                }
                if(Directory.Exists(task.filePath) == false)
                {
                    Directory.CreateDirectory(task.filePath);
                }
                FileStream fsFinal = new FileStream(task.filePath + "\\" + task.fileName, FileMode.Create);
                foreach (DownloadThreadInfo dti in threadInfoes)
                {
                    FileStream fsTemp = new FileStream(dti.tempFileName, FileMode.Open);
                    byte[] bytes = new byte[512];
                    int readCount = fsTemp.Read(bytes, 0, 512);
                    while (readCount > 0)
                    {
                        fsFinal.Write(bytes, 0, readCount);
                        readCount = fsTemp.Read(bytes, 0, 512);
                    }
                    fsTemp.Close();
                }
                fsFinal.Close();
                task.finishedTime = DateTime.Now;
                Thread deleteTempFileThread = new Thread(new ThreadStart(deleteTempFiles));
                deleteTempFileThread.Start();

                Console.WriteLine("合并文件结束:" + task.finishedTime.ToString());
                Console.WriteLine();

            }
            catch (System.Exception ex)
            {
            }
        }

        public void deleteTempFiles()
        {
            //删除临时文件
            Thread.Sleep(3000);
            foreach (DownloadThreadInfo dti in threadInfoes)
            {
                try
                {
                    File.Delete(dti.tempFileName);
                }
                catch (System.Exception ex)
                {
                }
            }
        }

        private List<DownloadThreadInfo> divideTask(int count)
        {
            List<DownloadThreadInfo> list = new List<DownloadThreadInfo>();
            HttpWebRequest myRequest = null;
            HttpWebResponse myHttpResponse = null;
            myRequest = (HttpWebRequest)WebRequest.Create(task.downloadUrl);
            myHttpResponse = (HttpWebResponse)myRequest.GetResponse();
            //Stream streamTest = myHttpResponse.GetResponseStream();


            


            long totalLength = myHttpResponse.ContentLength;
            if(totalLength == -1)
            {
                Stream stream = myHttpResponse.GetResponseStream();
                StreamReader sr = new StreamReader(stream);
                String s = sr.ReadToEnd();
                totalLength = s.Length;
                task.fileSize = getSize(totalLength);
                this.totalBytes = totalLength;
                myHttpResponse.Close();

                DownloadThreadInfo dti = new DownloadThreadInfo();
                dti.from = -1;
                dti.to = -1;
                if (Directory.Exists(tempFolder) == false)
                {
                    Directory.CreateDirectory(tempFolder);
                }
                Random ran1 = new Random();
                dti.tempFileName = tempFolder + task.downloadId + "_" + ran1.Next(1000) + "_" + task.fileName + ".part1";
                dti.finished = false;
                dti.downloadedBytes = 0;
                list.Add(dti);
                return list;
            }
            task.fileSize = getSize(totalLength);
            this.totalBytes = totalLength;
            Console.WriteLine(this.totalBytes);


            myHttpResponse.Close();
            
            int eachTaskLength = (int)totalLength / count;
            int begin = 0;
            int i;
            Random ran = new Random();
            if (Directory.Exists(tempFolder) == false)
            {
                Directory.CreateDirectory(tempFolder);
            }
            for (i = 0; i < count - 1; i++)
            {
                DownloadThreadInfo dti = new DownloadThreadInfo();
                dti.from = begin;
                dti.to = begin + eachTaskLength - 1;
                begin = dti.to + 1;
                dti.tempFileName = tempFolder + task.downloadId + "_" + ran.Next(1000) + "_" + task.fileName + ".part" + (i + 1);
                dti.finished = false;
                dti.downloadedBytes = 0;
                list.Add(dti);
            }
            DownloadThreadInfo dtiLast = new DownloadThreadInfo();
            dtiLast.from = begin;
            dtiLast.to = (int)totalLength - 1;
            begin = dtiLast.to + 1;
            dtiLast.tempFileName = tempFolder + task.downloadId + "_" + ran.Next(1000) + "_" + task.fileName + ".part" + (i + 1);
            dtiLast.finished = false;
            dtiLast.downloadedBytes = 0;
            list.Add(dtiLast);
            return list;
            /*catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
                return null;
            }*/

        }

        private void downloadBytes(object obj) //下载URL里面指定位置的内容到文件中
        {
            FileStream fs = null;
            try
            {
                DownloadThreadInfo dti = (DownloadThreadInfo)obj;
                int from = dti.from;
                int to = dti.to;
                String uri = task.downloadUrl;
                String fileName = dti.tempFileName;
                dti.downloading = true;

                if (File.Exists(fileName))
                {
                    fs = new FileStream(fileName, FileMode.Append, FileAccess.Write);
                    from = from + dti.downloadedBytes;
                }
                else
                {
                    fs = new FileStream(fileName, FileMode.Create);
                    dti.downloadedBytes = 0;
                }

                HttpWebRequest myRequest = null;
                HttpWebResponse myHttpResponse = null;
                myRequest = (HttpWebRequest)WebRequest.Create(uri);

                if(from != -1 && to != -1)
                {
                    myRequest.AddRange(from, to);
                }
                

                myHttpResponse = (HttpWebResponse)myRequest.GetResponse();
                long totalLength = myHttpResponse.ContentLength;
                /*if (totalLength == -1)
                {
                    Stream stream = myHttpResponse.GetResponseStream();
                    StreamReader sr = new StreamReader(stream);
                    String s = sr.ReadToEnd();
                    totalLength = s.Length;
                }*/
                Console.WriteLine(totalLength);


                Stream ns = myHttpResponse.GetResponseStream();
                //int recvTotal = 0;
                byte[] nbytes = new byte[512];
                int nreadsize = ns.Read(nbytes, 0, 512);
                //recvTotal += nreadsize;
                while (nreadsize > 0 && dti.downloading == true)
                {
                    //Console.WriteLine(recvTotal + "/" + totalLength);
                    fs.Write(nbytes, 0, nreadsize);
                    dti.downloadedBytes += nreadsize;
                    nreadsize = ns.Read(nbytes, 0, 512);
                    //recvTotal += nreadsize;
                }
                //previousDuratoinsReceived += dti.downloadedBytes;
                Console.WriteLine("thread end, recevied {0}", dti.downloadedBytes);
                if (dti.downloading == true)
                {
                    dti.finished = true;
                }
                myHttpResponse.Close();
                fs.Close();
            }
            catch (System.Net.WebException ex)
            {
                //下载时网络断开
                pauseDownload();
                if(errorEvent != null)
                {
                    errorEvent(DownloadError.ErrorCode.DOWNLOAD_NETWORK_DISCONNECTED);
                }
                Console.WriteLine(ex.ToString());
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

        private String getSize(long totalBytes)
        {
            String size = "";
            double result;
            result = ((double)totalBytes) / (1024L * 1024L * 1024L);
            if (result >= 1)
            {
                size += (result.ToString("F1") + "G");
                return size;
            }
            result = ((double)totalBytes) / (1024L * 1024L);
            if (result >= 1)
            {
                size += (result.ToString("F1") + "M");
                return size;
            }
            result = ((double)totalBytes) / (1024L);
            if (result >= 1)
            {
                size += (result.ToString("F1") + "Kb");
                return size;
            }
            size += (totalBytes.ToString() + "B");
            return size;

        }

        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //current speed byte/second
            long currentTotalRecv = 0;
            foreach (DownloadThreadInfo dti in threadInfoes)
            {
                currentTotalRecv += dti.downloadedBytes;
            }
            long currentSecondReceviedBytes = currentTotalRecv - oneSecondPreviousBytesReceviced;
            oneSecondPreviousBytesReceviced = currentTotalRecv;
            String currentSpeed = "";
            double result;
            result = ((double)currentSecondReceviedBytes) / (1024L * 1024L * 1024L);
            if (result >= 1)
            {
                currentSpeed += (result.ToString("F1") + "G/s");
                task.taskStatus.downloadCurrentSpeed = currentSpeed;
            }
            else
            {
                result = ((double)currentSecondReceviedBytes) / (1024L * 1024L);
                if (result >= 1)
                {
                    currentSpeed += (result.ToString("F1") + "M/s");
                    task.taskStatus.downloadCurrentSpeed = currentSpeed;
                }
                else
                {
                    result = ((double)currentSecondReceviedBytes) / (1024L);
                    if (result >= 1)
                    {
                        currentSpeed += (result.ToString("F1") + "Kb/s");
                        task.taskStatus.downloadCurrentSpeed = currentSpeed;
                    }
                    else
                    {
                        currentSpeed += (currentSecondReceviedBytes.ToString() + "B/s");
                        task.taskStatus.downloadCurrentSpeed = currentSpeed;
                    }
                }

            }


            //duration
            long secondsCost = previousTotalDuration;
            if (task.taskStatus.taskStatus == TaskStatus.STATUS_DOWNLOADING)
            {
                DateTime currentTime = DateTime.Now;
                TimeSpan ts = currentTime - previousStartTime;
                secondsCost += (ts.Days * (24 * 60 * 60) + ts.Hours * (60 * 60) + ts.Minutes * 60 + ts.Seconds);
            }

            String duration = "";
            int day = (int)((secondsCost) / (24 * 60 * 60));
            long rest = secondsCost % (24 * 60 * 60);
            if (day != 0)
            {
                duration += (day.ToString("") + "天");
            }
            int hour = (int)((rest) / (60 * 60));
            rest = rest % (60 * 60);
            if (!(day == 0 && hour == 0))
            {
                duration += (hour.ToString("") + "小时");
            }
            int minute = (int)((rest) / 60);
            rest = rest % 60;
            if (!(day == 0 && hour == 0 && minute == 0))
            {
                duration += (minute.ToString("") + "分钟");
            }
            duration += (rest.ToString() + "秒");
            task.taskStatus.downloadDuration = duration;

            //average speed
            long averageOneSecondReceivedBytes = (currentTotalRecv) / secondsCost;
            String averageSpeed = "";
            result = ((double)averageOneSecondReceivedBytes) / (1024L * 1024L * 1024L);
            if (result >= 1)
            {
                averageSpeed += (result.ToString("F1") + "G/s");
                task.taskStatus.downloadAverageSpeed = averageSpeed;
            }
            else
            {
                result = ((double)averageOneSecondReceivedBytes) / (1024L * 1024L);
                if (result >= 1)
                {
                    averageSpeed += (result.ToString("F1") + "M/s");
                    task.taskStatus.downloadAverageSpeed = averageSpeed;
                }
                else
                {
                    result = ((double)averageOneSecondReceivedBytes) / (1024L);
                    if (result >= 1)
                    {
                        averageSpeed += (result.ToString("F1") + "Kb/s");
                        task.taskStatus.downloadAverageSpeed = averageSpeed;
                    }
                    else
                    {
                        averageSpeed += (averageOneSecondReceivedBytes.ToString() + "B/s");
                        task.taskStatus.downloadAverageSpeed = averageSpeed;
                    }
                }

            }



            //progress
            double percentage = ((double)(currentTotalRecv)) / totalBytes;
            task.taskStatus.downloadProgress = (int)(percentage * 100);


            Console.WriteLine("已用时:" + task.taskStatus.downloadDuration);
            Console.WriteLine("平均速度:" + task.taskStatus.downloadAverageSpeed);
            Console.WriteLine("即时速度:" + task.taskStatus.downloadCurrentSpeed);
            Console.WriteLine("进度:" + task.taskStatus.downloadProgress);
            Console.WriteLine();
        }

        private bool isValidFileName(string fileName)   //判断文件名是否合法
        {
            bool isValid = true;
            string errChar = "\\/:*?\"<>|";  
            if (string.IsNullOrEmpty(fileName))
            {
                isValid = false;
            }
            else
            {
                for (int i = 0; i < errChar.Length; i++)
                {
                    if (fileName.Contains(errChar[i].ToString()))
                    {
                        isValid = false;
                        break;
                    }
                }
            }
            return isValid;
        }


    }

    [Serializable]
    class DownloadThreadInfo
    {
        public int from { get; set; }
        public int to { get; set; }
        public bool finished { get; set; }
        public string tempFileName { get; set; }
        public int downloadedBytes { get; set; }
        public bool downloading { get; set; }
    }

    class FileNameInvalidException : Exception
    {

    }

    class PathInvalidException : Exception
    {

    }
}
